<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟滚动实现</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <style>
        * {
            padding: 0;
            margin: 0;
        }
        .view-container {
            overflow: hidden;
            padding: 5px;
            margin: 5px;
            border: 1px solid #ccc;
        }
        .list-scroll-wrapper {
            height: 100%;
            overflow: auto;
        }
        .row-wrapper {
            height: 30px;
            line-height: 30px;
        }
        .virtual-container {
            position: relative;
        }
    </style>
    
</head>
<body>
    <div id="app">
        <input v-model="search" @input="inputMethod"/>
        <div class="view-container" :style="{height: viewHeight + 'px'}"> <!-- 最外层的可视 -->
            <div class="list-scroll-wrapper" @scroll="handleScroll" ref="listScrollWrapper"> <!-- 滚动层 -->
                <div class="virtual-container" :style="{height: virtualHeight + 'px'}"> <!-- 虚拟高度层 -->
                    <div style="position: absolute; left: 0;" :style="{top: viewTop + 'px'}"> <!-- 绝对定位，显示数据，否则会随着滚动而滚动 -->
                        <p v-for="item in list" :key="item.id" class="row-wrapper"> {{item.val}}</p> <!-- 实际显示的表格 -->
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        // 目标：加载10W行数据
        // 实现：采用虚拟滚动技术
        // 1.创建显示的列表
        // 2.控制显示的高度
        // 3.根据数据虚拟出一个高度
        // 4.虚拟的高度外包一层，用于实现滚动
        // 5.实际的显示列表采用绝对定位，实时改变top，保证一直可视
        new Vue({
            el: '#app',
            data: {
                data: [], // 所有数据
                list: [], // 当前显示的可视数据
                rowHeight: 30, // 行高
                viewHeight: 300, // 可视列表高度
                virtualHeight: 300, // 虚拟的高度，
                viewTop: 0,
                search:'',
                handleData:[]
            },
            created() {
              this.mockData()
              this.handleData = [...this.data]
                this.refreshList()
            },
            watch:{
              search(newVal){
                this.inputMethod();
                this.calcHeight();
                setTimeout(()=>{
                    this.$refs.listScrollWrapper.scrollTop = this.$refs.listScrollWrapper.scrollTop + 1
                },100)
              }
            },
            mounted() {
             
              this.inputMethod()
              this.calcHeight();
            },
            methods: {
                mockData() { // 造数据
                    const data = []
                    for(let i=0; i<300000; i++) {
                        data.push({ 
                            id: i,
                            val: 'data-' + i
                        })
                    }
                    this.data = data
                },
                refreshList(start = 0, end = 20) { // 根据传入的值去渲染数据    
                    const list = []
                    end = end < this.handleData.length ? end : this.handleData.length
                    for(let i=start; i<end; i++) {
                        list.push(this.handleData[i])
                    }
                    this.list = list
                },
                handleScroll(e) {
                    const scrollTop = e.target.scrollTop
                    this.viewTop = scrollTop
                    // 根据滚动位置进行计算显示的数据
                    const start = parseInt(scrollTop / this.rowHeight);
                    this.refreshList(start, start + 20)
                },
                calcHeight(){
                  let height = this.handleData.length * this.rowHeight
                  if(height<300){
                    this.virtualHeight = 300
                    this.list = this.handleData
                  }
                  this.virtualHeight = height

                },
                inputMethod(){
                  this.handleData = this.data.filter(item=>{
                    return item.val.includes(this.search)
                  })
                  if(!this.handleData.length){
                    this.list = []
                  }
                }
                
            }
        })
    </script>
</body>
</html>
